"""
A `Block` is a single link in the chain that is Ethereum. Each `Block` contains
a `Header` and zero or more transactions. Each `Header` contains associated
metadata like the block number, parent block hash, and how much gas was
consumed by its transactions.

Together, these blocks form a cryptographically secure journal recording the
history of all state transitions that have happened since the genesis of the
chain.
"""
from dataclasses import dataclass
from typing import Tuple

from ethereum_rlp import rlp
from ethereum_types.bytes import Bytes, Bytes8, Bytes32
from ethereum_types.frozen import slotted_freezable
from ethereum_types.numeric import U256, Uint

from ..crypto.hash import Hash32
from .fork_types import Address, Bloom, Root
from .transactions import (
    AccessListTransaction,
    FeeMarketTransaction,
    LegacyTransaction,
    Transaction,
)


@slotted_freezable
@dataclass
class Header:
    """
    Header portion of a block on the chain, containing metadata and
    cryptographic commitments to the block's contents.
    """

    parent_hash: Hash32
    """
    Hash ([`keccak256`]) of the parent block's header, encoded with [RLP].

    [`keccak256`]: ref:ethereum.crypto.hash.keccak256
    [RLP]: https://ethereum.github.io/ethereum-rlp/src/ethereum_rlp/rlp.py.html
    """

    ommers_hash: Hash32
    """
    Hash ([`keccak256`]) of the ommers (uncle blocks) in this block, encoded
    with [RLP].

    [`keccak256`]: ref:ethereum.crypto.hash.keccak256
    [RLP]: https://ethereum.github.io/ethereum-rlp/src/ethereum_rlp/rlp.py.html
    """

    coinbase: Address
    """
    Address of the miner (or validator) who mined this block.

    The coinbase address receives the block reward and the priority fees (tips)
    from included transactions. Base fees (introduced in [EIP-1559]) are burned
    and do not go to the coinbase.

    [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
    """

    state_root: Root
    """
    Root hash ([`keccak256`]) of the state trie after executing all
    transactions in this block. It represents the state of the Ethereum Virtual
    Machine (EVM) after all transactions in this block have been processed. It
    is computed using the [`state_root()`] function, which computes the root
    of the Merkle-Patricia [Trie] representing the Ethereum world state.

    [`keccak256`]: ref:ethereum.crypto.hash.keccak256
    [`state_root()`]: ref:ethereum.arrow_glacier.state.state_root
    [Trie]: ref:ethereum.arrow_glacier.trie.Trie
    """

    transactions_root: Root
    """
    Root hash ([`keccak256`]) of the transactions trie, which contains all
    transactions included in this block in their original order. It is computed
    using the [`root()`] function over the Merkle-Patricia [trie] of
    transactions as the parameter.

    [`keccak256`]: ref:ethereum.crypto.hash.keccak256
    [`root()`]: ref:ethereum.arrow_glacier.trie.root
    [Trie]: ref:ethereum.arrow_glacier.trie.Trie
    """

    receipt_root: Root
    """
    Root hash ([`keccak256`]) of the receipts trie, which contains all receipts
    for transactions in this block. It is computed using the [`root()`]
    function over the Merkle-Patricia [trie] constructed from the receipts.

    [`keccak256`]: ref:ethereum.crypto.hash.keccak256
    [`root()`]: ref:ethereum.arrow_glacier.trie.root
    [Trie]: ref:ethereum.arrow_glacier.trie.Trie
    """

    bloom: Bloom
    """
    Bloom filter for logs generated by transactions in this block.
    Constructed from all logs in the block using the [logs bloom] mechanism.

    [logs bloom]: ref:ethereum.arrow_glacier.bloom.logs_bloom
    """

    difficulty: Uint
    """
    Difficulty of the block, used in Proof-of-Work to determine the effort
    required to mine the block. This value adjusts over time to maintain a
    relatively constant block time and is computed using the
    [`calculate_block_difficulty()`] function.

    [`calculate_block_difficulty()`]:
    ref:ethereum.arrow_glacier.fork.calculate_block_difficulty
    """

    number: Uint
    """
    Block number, (height) in the chain.
    """

    gas_limit: Uint
    """
    Maximum gas allowed in this block. Pre [EIP-1559], this was the maximum
    gas that could be consumed by all transactions in the block. Post
    [EIP-1559], this is still the maximum gas limit, but the base fee per gas
    is also considered when calculating the effective gas limit. This can be
    [adjusted by a factor of 1/1024] from the previous block's gas limit, up
    until a maximum of 30 million gas.

    [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
    [adjusted by a factor of 1/1024]:
    https://ethereum.org/en/developers/docs/blocks/
    """

    gas_used: Uint
    """
    Total gas used by all transactions in this block.
    """

    timestamp: U256
    """
    Timestamp of when the block was mined, in seconds since the unix epoch.
    """

    extra_data: Bytes
    """
    Arbitrary data included by the miner.
    """

    mix_digest: Bytes32
    """
    Mix hash used in the mining process, which is a cryptographic commitment
    to the block's contents. It [validates] that PoW was done on the correct
    block.

    [validates]: ref:ethereum.arrow_glacier.fork.validate_proof_of_work
    """

    nonce: Bytes8
    """
    Nonce used in the mining process, which is a value that miners
    increment to find a valid block hash. This is also used to [validate] the
    proof-of-work for this block.

    [validate]: ref:ethereum.arrow_glacier.fork.validate_proof_of_work
    """

    base_fee_per_gas: Uint
    """
    Base fee per gas for transactions in this block, introduced in
    [EIP-1559]. This is the minimum fee per gas that must be paid for a
    transaction to be included in this block.

    [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
    """


@slotted_freezable
@dataclass
class Block:
    """
    A complete block on Ethereum, which is composed of a block [`header`],
    a list of transactions, and a list of ommers (also known as uncle blocks).

    The block [`header`] includes PoW-specific fields such as `difficulty`,
    `nonce`, and `ommersHash`, which relate to the mining process. The
    `coinbase` field denotes the address receiving mining and transaction
    fees.

    The header also contains commitments to the current state (`stateRoot`),
    the transactions (`transactionsRoot`), and the transaction receipts
    (`receiptsRoot`). It also includes a bloom filter which summarizes log
    data from the transactions.

    Ommers are used to provide rewards for near-valid mined blocks that didn't
    become part of the canonical chain.

    [`header`]: ref:ethereum.arrow_glacier.blocks.Header
    """

    header: Header
    """
    The block header containing metadata and cryptographic commitments. Refer
    [headers] for more details on the fields included in the header.

    [headers]: ref:ethereum.arrow_glacier.blocks.Header
    """

    transactions: Tuple[Bytes | LegacyTransaction, ...]
    """
    A tuple of transactions included in this block. Each transaction can be
    any of a legacy transaction, an access list transaction, or a fee market
    transaction.
    """

    ommers: Tuple[Header, ...]
    """
    A tuple of ommers (uncle blocks) included in this block. Ommers are
    blocks that were mined at the same time as this block but were not
    included in the main chain.
    """


@slotted_freezable
@dataclass
class Log:
    """
    Data record produced during the execution of a transaction. Logs are used
    by smart contracts to emit events (using the EVM log opcodes ([`LOG0`],
    [`LOG1`], [`LOG2`], [`LOG3`] and [`LOG4`]), which can be efficiently
    searched using the bloom filter in the block header.

    [`LOG0`]: ref:ethereum.arrow_glacier.vm.instructions.log.log0
    [`LOG1`]: ref:ethereum.arrow_glacier.vm.instructions.log.log1
    [`LOG2`]: ref:ethereum.arrow_glacier.vm.instructions.log.log2
    [`LOG3`]: ref:ethereum.arrow_glacier.vm.instructions.log.log3
    [`LOG4`]: ref:ethereum.arrow_glacier.vm.instructions.log.log4
    """

    address: Address
    """
    The address of the contract that emitted the log.
    """

    topics: Tuple[Hash32, ...]
    """
    A tuple of up to four topics associated with the log, used for filtering.
    """

    data: Bytes
    """
    The data payload of the log, which can contain any arbitrary data.
    """


@slotted_freezable
@dataclass
class Receipt:
    """
    Result of a transaction execution. Receipts are included in the receipts
    trie.
    """

    succeeded: bool
    """
    Whether the transaction execution was successful.
    """

    cumulative_gas_used: Uint
    """
    Total gas used in the block up to and including this transaction.
    """

    bloom: Bloom
    """
    Bloom filter for logs generated by this transaction. This is a 2048-byte
    bit array that allows for efficient filtering of logs.
    """

    logs: Tuple[Log, ...]
    """
    A tuple of logs generated by this transaction. Each log contains the
    address of the contract that emitted it, a tuple of topics, and the data
    payload.
    """


def encode_receipt(tx: Transaction, receipt: Receipt) -> Bytes | Receipt:
    r"""
    Encodes a transaction receipt based on the transaction type.

    The encoding follows the same format as transactions encoding, where:
    - AccessListTransaction receipts are prefixed with `b"\x01"`.
    - FeeMarketTransaction receipts are prefixed with `b"\x02"`.
    - LegacyTransaction receipts are returned as is.
    """
    if isinstance(tx, AccessListTransaction):
        return b"\x01" + rlp.encode(receipt)
    elif isinstance(tx, FeeMarketTransaction):
        return b"\x02" + rlp.encode(receipt)
    else:
        return receipt


def decode_receipt(receipt: Bytes | Receipt) -> Receipt:
    r"""
    Decodes a receipt from its serialized form.

    The decoding follows the same format as transactions decoding, where:
    - Receipts prefixed with `b"\x01"` are decoded as AccessListTransaction
    receipts.
    - Receipts prefixed with `b"\x02"` are decoded as FeeMarketTransaction
    receipts.
    - LegacyTransaction receipts are returned as is.
    """
    if isinstance(receipt, Bytes):
        assert receipt[0] in (1, 2)
        return rlp.decode_to(Receipt, receipt[1:])
    else:
        return receipt
